package com.zj.baseproxy.handler;

import com.zj.baseproxy.model.PageInfo;
import com.zj.baseproxy.utils.ClassUtil;
import com.zj.baseproxy.utils.MybatisUtil;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
 * ck动态代理
 *
 * @author arthur_zhou
 * @date 2024/2/28 14:32
 */
public abstract class AbstractXmlInvocationHandler implements InvocationHandler {
    private static final Logger log = LoggerFactory.getLogger(AbstractXmlInvocationHandler.class);

    private final Configuration configuration;

    protected AbstractXmlInvocationHandler(Configuration configuration) {
        this.configuration = configuration;
    }


    /**
     * 动态代理调用方法
     *
     * @param proxy  生成的代理对象
     * @param method 代理的方法
     * @param args   方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        String id = method.getDeclaringClass().getName() + "." + method.getName();
        Class<?> returnType = method.getReturnType();
        MappedStatement ms;
        try {
            // 获取xml对应方法数据
            ms = configuration.getMappedStatement(id);
        } catch (IllegalArgumentException e) {
            log.error("=>{}方法未找到对应的xml信息", id);
            return ClassUtil.cast(null, returnType);
        }
        // 获取xml里的返回类型
        List<ResultMap> resultMaps = ms.getResultMaps();
        if (CollectionUtils.isEmpty(resultMaps)) {
            return ClassUtil.cast(null, returnType);
        }
        // 一般是第一个？
        ResultMap resultMap = resultMaps.get(0);
        // 获取方法上的入参
        Parameter[] parameters = method.getParameters();
        // 是否分页
        AtomicReference<PageInfo> atomicReference = new AtomicReference<>();
        // 赋值xml名字对应的入参
        Object parameterObject = getParams(args, parameters, atomicReference);
        PageInfo pageInfo = atomicReference.get();
        // 半成品sql
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        String sql = MybatisUtil.showSql(configuration, boundSql);
        log.debug("=>{}方法对应的sql为\n{}", id, sql);
        return packagePage(id, sql, resultMap.getType(), returnType, pageInfo);
    }

    /**
     * 组装分页返回结果
     *
     * @param id            方法全路径
     * @param sql           sql
     * @param xmlResultType xml里的resultType
     * @param returnType    方法上的返回class
     * @param pageInfo      分页信息
     * @return 结果
     */
    public abstract <T> T packagePage(String id, String sql, Class<?> xmlResultType, Class<T> returnType, PageInfo pageInfo);

    /**
     * 获取放入sql的入参
     *
     * @param args            传入的数据
     * @param parameters      方法的入参信息
     * @param atomicReference 分页信息
     * @return 放入sql的入参
     */
    private Object getParams(Object[] args, Parameter[] parameters, AtomicReference<PageInfo> atomicReference) {
        if (Objects.isNull(args) || Objects.isNull(parameters) || args.length == 0 || parameters.length != args.length) {
            return null;
        }
        // 入参只有一个且没有加@Param时，直接返回第一个
        if (args.length == 1) {
            if (PageInfo.class == parameters[0].getType()) {
                atomicReference.set((PageInfo) args[0]);
            }
            Param param = parameters[0].getAnnotation(Param.class);
            if (Objects.isNull(param) || StringUtils.isEmpty(param.value())) {
                return args[0];
            }
        }
        // 多个入参用map收集
        Map<String, Object> paramMap = new HashMap<>(args.length);
        // 优先使用@Param里的名字
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Class<?> type = parameter.getType();
            if (PageInfo.class == type) {
                atomicReference.set((PageInfo) args[i]);
            }
            Param param = parameter.getAnnotation(Param.class);
            String name;
            if (Objects.isNull(param) || StringUtils.isEmpty(name = param.value())) {
                name = parameter.getName();
            }
            paramMap.put(name, args[i]);
        }
        return paramMap;
    }

}
